/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package co.com.claro.administracionSolicitudes.dao;

import co.com.claro.administracionSolicitudes.dao.exceptions.NonexistentEntityException;
import co.com.claro.administracionSolicitudes.dao.exceptions.RollbackFailureException;
import co.com.claro.administracionSolicitudes.dto.UsuarioDto;
import java.io.Serializable;
import javax.persistence.Query;
import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import co.com.claro.administracionSolicitudes.entity.Gerencia;
import co.com.claro.administracionSolicitudes.entity.Grupo;
import co.com.claro.administracionSolicitudes.entity.HistoricoUsuario;
import co.com.claro.administracionSolicitudes.entity.enums.MovimientosEnum;
import co.com.claro.administracionSolicitudes.entity.Usuario;
import co.com.claro.administracionSolicitudes.entity.enums.EstadoActividadEnum;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;

/**
 *
 * @author Mike
 */
public class UsuarioJpaController implements Serializable {

    public UsuarioJpaController() {

    }
    @Inject
    private HistoricoUsuarioJpaController dao;
    @PersistenceContext
    private EntityManager em;
    private HistoricoUsuario historico;

    public void create(Usuario usuario) throws RollbackFailureException, Exception {
        try {
            List<Grupo> attachedGrupoList = new ArrayList<Grupo>();
            for (Grupo grupoListGrupoToAttach : usuario.getGrupoList()) {
                grupoListGrupoToAttach = em.getReference(grupoListGrupoToAttach.getClass(), grupoListGrupoToAttach.getIdGrupo());
                attachedGrupoList.add(grupoListGrupoToAttach);
            }
            usuario.setGrupoList(attachedGrupoList);
            em.persist(usuario);
            for (Grupo grupoListGrupo : usuario.getGrupoList()) {
                grupoListGrupo.getUsuarioList().add(usuario);
                grupoListGrupo = em.merge(grupoListGrupo);
            }
            historico = this.crearHistorico(usuario, MovimientosEnum.CREAR);
            dao.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    public void edit(Usuario usuario) throws NonexistentEntityException, RollbackFailureException, Exception {
        String usuarioStr = usuario.getUsuario();
        try {
            Usuario persistentUsuario = em.find(Usuario.class, usuario.getIdUsuario());
            List<Grupo> grupoListOld = persistentUsuario.getGrupoList();
            List<Grupo> grupoListNew = usuario.getGrupoList();
            List<Grupo> attachedGrupoListNew = new ArrayList<Grupo>();
            for (Grupo grupoListNewGrupoToAttach : grupoListNew) {
                grupoListNewGrupoToAttach = em.getReference(grupoListNewGrupoToAttach.getClass(), grupoListNewGrupoToAttach.getIdGrupo());
                attachedGrupoListNew.add(grupoListNewGrupoToAttach);
            }
            grupoListNew = attachedGrupoListNew;
            usuario.setGrupoList(grupoListNew);
            usuario = em.merge(usuario);
            usuario.setUsuario(usuarioStr);
            for (Grupo grupoListOldGrupo : grupoListOld) {
                if (!grupoListNew.contains(grupoListOldGrupo)) {
                    grupoListOldGrupo.getUsuarioList().remove(usuario);
                    grupoListOldGrupo = em.merge(grupoListOldGrupo);
                }
            }
            for (Grupo grupoListNewGrupo : grupoListNew) {
                if (!grupoListOld.contains(grupoListNewGrupo)) {
                    grupoListNewGrupo.getUsuarioList().add(usuario);
                    grupoListNewGrupo = em.merge(grupoListNewGrupo);
                }
            }
            historico = this.crearHistorico(usuario, MovimientosEnum.MODIFICAR);
            dao.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    public void destroy(Integer id, Usuario usuarioOriginal) throws NonexistentEntityException, RollbackFailureException, Exception {

        try {
            Usuario usuario;
            try {
                usuario = em.getReference(Usuario.class, id);
                usuario.getIdUsuario();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The usuario with id " + id + " no longer exists.", enfe);
            }
            em.remove(usuario);
            historico = this.crearHistorico(usuario, MovimientosEnum.BORRAR);
            dao.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * Método que actualiza el estado de la entidad y lo pasa de activo a
     * inactivo haciendo un borrado lógico de la entidad
     *
     * @param usuario
     * @throws NonexistentEntityException
     * @throws RollbackFailureException
     * @throws Exception
     */
    public void borradoLogicoUsuario(Usuario usuario) throws NonexistentEntityException, RollbackFailureException, Exception {
        String username = usuario.getUsuario();
        try {
            usuario.setActivo(EstadoActividadEnum.INACTIVO.getId());
            usuario.setEstado(EstadoActividadEnum.INACTIVO.getId());
            usuario = em.merge(usuario);
            usuario.setUsuario(username);
            historico = this.crearHistorico(usuario, MovimientosEnum.BORRAR);
            dao.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    public List<Usuario> findUsuariosNoPaginacion(UsuarioDto dataTransfer) {
        return findUsuarios(false, -1, -1, dataTransfer);
    }

    public List<Usuario> findUsuariosPaginacion(int firstResult, int maxResult) {
        return findUsuarios(true, firstResult, maxResult, null);
    }

    public List<Usuario> findUsuariosPaginacion(int firstResult, int maxResult, UsuarioDto dataTransfer) {
        return findUsuarios(true, firstResult, maxResult, dataTransfer);
    }

    private List<Usuario> findUsuarios(boolean paginado, int firstResult, int maxResults, UsuarioDto dataTransfer) {
        //SELECT u FROM Usuario u
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Usuario> cQuery = builder.createQuery(Usuario.class);
        Root<Usuario> u = cQuery.from(Usuario.class);
        cQuery.select(u);

        List<Predicate> predicados = construirPredicados(dataTransfer, u);

        if (!predicados.isEmpty()) {
            //WHERE [...]
            cQuery.where(predicados.toArray(new Predicate[0]));
        }
        if (dataTransfer.getCampoSorteado() != null) {

            if (dataTransfer.getCampoSorteado().contains(".")) {
                String[] split = dataTransfer.getCampoSorteado().split("\\.");

                Path order = u.get(split[0]);

                for (int i = 1; i < split.length; i++) {
                    order = order.get(split[i]);
                }

                //ORDER BY [...]
                if (dataTransfer.isAscendiente()) {
                    cQuery.orderBy(builder.asc(order));
                } else {
                    cQuery.orderBy(builder.desc(order));
                }

            } else {
                //ORDER BY [...]
                if (dataTransfer.isAscendiente()) {
                    cQuery.orderBy(builder.asc(u.get(dataTransfer.getCampoSorteado())));
                } else {
                    cQuery.orderBy(builder.desc(u.get(dataTransfer.getCampoSorteado())));
                }
            }
        }

        Query query = em.createQuery(cQuery);

        if (paginado) {
            query.setMaxResults(maxResults);
            query.setFirstResult(firstResult);
        }

        return query.getResultList();
    }

    private List<Predicate> construirPredicados(UsuarioDto dataTransfer, Root<Usuario> root) {
        CriteriaBuilder builder = em.getCriteriaBuilder();

        List<Predicate> predicados = new ArrayList<Predicate>();

        Predicate nombres, apellidos, idCargo, idCiudad, correo,
                numeroDocumento, idGerencia, username, activo, rol,
                estado;

        if (dataTransfer.getNombres() != null) {
            // LOWER(u.nombres) LIKE :nombres
            nombres = builder.like(builder.lower(root.<String>get("nombres")),
                    "%" + dataTransfer.getNombres().toLowerCase() + "%");
            predicados.add(nombres);
        }
        if (dataTransfer.getApellidos() != null) {
            // LOWER(u.apellidos) LIKE :apellidos
            apellidos = builder.like(builder.lower(root.<String>get("apellidos")),
                    "%" + dataTransfer.getApellidos().toLowerCase() + "%");
            predicados.add(apellidos);
        }
        if (dataTransfer.getIdCargo() != null) {
            // u.cargo.idCargo = :idCargo
            idCargo = builder.equal(root.get("cargo").get("idCargo"), dataTransfer.getIdCargo());
            predicados.add(idCargo);
        }
        if (dataTransfer.getIdCiudad() != null) {
            // u.ciudad.idCiudad = :idCiudad
            idCiudad = builder.equal(root.get("ciudad").get("idCiudad"), dataTransfer.getIdCiudad());
            predicados.add(idCiudad);
        }
        if (dataTransfer.getCorreo() != null) {
            // LOWER(u.correo) LIKE :correo
            correo = builder.like(builder.lower(root.<String>get("correo")),
                    "%" + dataTransfer.getCorreo().toLowerCase() + "%");
            predicados.add(correo);
        }
        if (dataTransfer.getNumeroDocumento() != null) {
            // u.numeroDocumento = :numeroDocumento
            numeroDocumento = builder.equal(root.get("numeroDocumento"), dataTransfer.getNumeroDocumento());
            predicados.add(numeroDocumento);
        }
        if (dataTransfer.getIdGerencia() != null) {
            // u.gerencia.idGerencia = idGerencia
            idGerencia = builder.equal(root.get("gerencia").get("idGerencia"), dataTransfer.getIdGerencia());
            predicados.add(idGerencia);
        }
        if (dataTransfer.getUsername() != null) {
            // LOWER(u.username) LIKE :username
            username = builder.like(builder.lower(root.<String>get("username")),
                    "%" + dataTransfer.getUsername().toLowerCase() + "%");
            predicados.add(username);
        }
        if (dataTransfer.getActivo() != null) {
            // u.activo = :activo
            activo = builder.equal(root.get("activo"), dataTransfer.getActivo());
            predicados.add(activo);
        }
        if (dataTransfer.getEstado() != null){
            // u.estado = :estado
            estado = builder.equal(root.get("estado"), dataTransfer.getEstado());
            predicados.add(estado);
        }

        return predicados;

    }

    public Usuario findUsuario(Integer id) {
        return em.find(Usuario.class, id);
    }

    public Usuario findUsuarioActivo(Integer id) {
        return (Usuario) em.createNamedQuery("Usuario.findUsuarioActivoById")
                .setParameter("idUsuario", id)
                .setParameter("activo", EstadoActividadEnum.ACTIVO.getId())
                .getSingleResult();
    }

    /**
     * Método que obtiene un usuario por su username y el estado de actividad
     *
     * @param username, nombre de usuario dado en el sistema
     * @param activo, estado de usuario en base de datos 1 = Inactivo; 2 = Activo
     * @param estado estado del usuario en admin solicitudes 1 = inactivo; 2 = activo
     * @return Usuario, el usuario correspondiente al username
     */
    public List<Usuario> findUsuariosByUsername(String username, Short activo, Short estado) {
        return em.createNamedQuery("Usuario.findUsuarioByUsername")
                .setParameter("username", "%" + username.toLowerCase() + "%")
                .setParameter("activo", activo)
                .setParameter("estado", estado)
                .getResultList();
    }

    public Usuario findUsuarioByUsernameExacto(String username, Short activo, Short estado) {
        List<Usuario> result = em.createNamedQuery("Usuario.findUsuarioByExactUsername").
                setParameter("username", username.toLowerCase())
                .setParameter("activo", activo)
                .setParameter("estado", estado)
                .getResultList();

        if (!result.isEmpty()) {
            return result.get(0);
        } else {
            return null;
        }
    }

    /**
     * Método que obtiene un usuario por su gerencia, el rol ejecutor o
     * administrador y el estado de actividad
     *
     * @param idGerencia, gerencia a la que pertenece el usuario
     * @param idEjecutor, rol del usuario
     * @param idAdministrador, rol administrador
     * @param activo, estado de actividad 1 = Inactivo; 2 = Activo
     * @return Usuario, el usuario correspondiente al username
     */
    public List<Usuario> findUsuarioByGerenciaAndGrupo(short idGerencia, short idEjecutor, short idAdministrador, Short activo) {
        Gerencia gerencia = new Gerencia(idGerencia);
//        Grupo ejecutor = new Grupo(idEjecutor);
//        Grupo administrador = new Grupo(idAdministrador);
        try {
            Query query = em.createNamedQuery("Usuario.findUsuarioByGerenciaAndGrupo");
            query.setParameter("gerencia", gerencia);
            query.setParameter("ejecutor", idEjecutor);
            query.setParameter("administrador", idAdministrador);
            query.setParameter("activo", activo);
            return query.getResultList();
        } catch (NoResultException nre) {
        }
        return null;
    }

    /**
     * Método que obtiene los usuarios por su gerencia
     *
     * @param idGerencia, gerencia a la que pertenece el usuario
     * @param activo, estado de actividad 1 = Inactivo; 2 = Activo
     * @return List<Usuario>, los usuarios correspondientes a la gerencia
     */
    public List<Usuario> findUsuarioByGerencia(short idGerencia, Short activo) {
        Gerencia gerencia = new Gerencia(idGerencia);

        try {
            Query query = em.createNamedQuery("Usuario.findUsuarioByGerencia");
            query.setParameter("gerencia", gerencia);
            query.setParameter("activo", activo);
            return query.getResultList();
        } catch (NoResultException nre) {
        }
        return null;
    }

    /**
     * Método que obtiene un usuario por su número de documento
     *
     * @param numeroDocumento, cédula del usuario a buscar
     * @param activo, estado de actividad 1 = Inactivo; 2 = Activo
     * @return Usuario, el usuario que coincide con el número de documento
     */
    public Usuario findUsuarioByNumeroDocumento(long numeroDocumento, Short activo) {

        try {
            Query query = em.createNamedQuery("Usuario.findByNumeroDocumento");
            query.setParameter("numeroDocumento", numeroDocumento);
            query.setParameter("activo", activo);
            query.setParameter("estado", activo);
            return (Usuario) query.getSingleResult();
        } catch (NoResultException nre) {
        }
        return null;
    }

    public List<Usuario> findUsuariosByGerenciaAndUsername(short idGerencia, String username, Short activo) {
        Gerencia gerencia = new Gerencia(idGerencia);

        return em.createNamedQuery("Usuario.findUsuarioByGerenciaAndUsername")
                .setParameter("gerencia", gerencia)
                .setParameter("username", "%" + username.toLowerCase() + "%")
                .setParameter("activo", activo)
                .getResultList();
    }

    public Usuario findUsuarioByGerenciaAndNumeroDocumento(short idGerencia, Long numeroDocumento, Short activo) {
        Gerencia gerencia = new Gerencia(idGerencia);

        return (Usuario) em.createNamedQuery("Usuario.findUsuarioByGerenciaAndNumeroDocumento")
                .setParameter("gerencia", gerencia)
                .setParameter("numeroDocumento", numeroDocumento)
                .setParameter("activo", activo)
                .getSingleResult();
    }

    public List<Usuario> findUsuariosByNombreApellido(String nombre, String apellido) {
        return em.createNamedQuery("Usuario.findUsuariosByNombreApellido")
                .setParameter("nombres", "%" + nombre.toLowerCase() + "%")
                .setParameter("apellidos", "%" + apellido.toLowerCase() + "%")
                .getResultList();
    }

    public List<Usuario> findUsuariosByNombre(String nombres) {
        return em.createNamedQuery("Usuario.findByNombres")
                .setParameter("nombres", "%" + nombres.toLowerCase() + "%")
                .getResultList();
    }

    public List<Usuario> findUsuariosByApellido(String apellidos) {
        return em.createNamedQuery("Usuario.findByApellidos")
                .setParameter("apellidos", "%" + apellidos.toLowerCase() + "%")
                .getResultList();
    }

    public List<Usuario> findUsuariosByGrupo(Short idGrupo, String sortCol, boolean asc) {

        Query q = null;

        if (asc) {
            q = em.createNativeQuery("SELECT * FROM USUARIO "
                    + "WHERE ID_USUARIO IN (SELECT ID_USUARIO FROM USUARIO_GRUPO WHERE ID_GRUPO = ?) "
                    + "ORDER BY ? ASC", Usuario.class)
                    .setParameter(1, idGrupo);
        } else {
            q = em.createNativeQuery("SELECT * FROM USUARIO "
                    + "WHERE ID_USUARIO IN (SELECT ID_USUARIO FROM USUARIO_GRUPO WHERE ID_GRUPO = ?) "
                    + "ORDER BY ? DESC", Usuario.class)
                    .setParameter(1, idGrupo);
        }

        if (sortCol.equals("numeroDocumento")) {
            q.setParameter(2, "NUMERO_DOCUMENTO");
        } else {
            q.setParameter(2, sortCol.toUpperCase());
        }

        return q.getResultList();
    }

    public int getUsuarioCount() {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
        Root<Usuario> rt = cq.from(Usuario.class);
        cq.select(em.getCriteriaBuilder().count(rt));
        Query q = em.createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

    public int getUsuarioCount(UsuarioDto dataTransfer) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery cQuery = builder.createQuery();
        Root<Usuario> root = cQuery.from(Usuario.class);
        cQuery.select(builder.count(root));

        List<Predicate> predicados = construirPredicados(dataTransfer, root);

        if (!predicados.isEmpty()) {
            //WHERE [...]
            cQuery.where(predicados.toArray(new Predicate[0]));
        }

        Query query = em.createQuery(cQuery);

        return ((Long) query.getSingleResult()).intValue();
    }

    /**
     * Método que crea el objeto HistoricoUsuario a partir de los parámetros
     * pasados
     *
     * @param Usuario, objeto que se piensa modificar
     * @param movimientosEnum, tipo de movimiento que se le va a hacer al objeto
     * @return HistoricoUsuario, objeto listo para insertar en la tabla de
     * historico usuario
     */
    private HistoricoUsuario crearHistorico(Usuario usuario, MovimientosEnum movimientosEnum) {
        HistoricoUsuario hist = new HistoricoUsuario();
        Calendar fechaActual = Calendar.getInstance();
        StringBuilder descripcion = new StringBuilder();

        descripcion.append(movimientosEnum.getNombre());
        descripcion.append(" ");
        descripcion.append(usuario.obtenerNombreClase());
        descripcion.append(" ");
        descripcion.append(usuario.getNombres());

        hist.setUsuario(usuario.getUsuario());
        hist.setFechaMovimiento(new Date(fechaActual.getTimeInMillis()));
        hist.setDescripcion(descripcion.toString());

        return hist;
    }

}
